/**
 * Copyright (c) 2010 icer He <icerhe@gmail.com>
 */
package com.icerhe.trigger.task;

import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Log;
import com.icerhe.trigger.R;
import com.icerhe.trigger.Consts;


/**
 * 
 * 定时执行任务,执行计划用类似CronTab的字符串描述
 * 形如 "* * * * * *"
 * 每个*的含义依次为
 * 分钟,小时,日期,月份,星期几,年份
 * 例如"15 8 * 1-5 *"的含义是每周1到周5的早晨八点十五分
 * 
 * 
 * @author icer
 *
 */
public abstract class CronTask extends Task {
	
	public static final Uri CONTENT_URI =
        Uri.parse("content://com.icerhe.trigger/crontask");

    private boolean runInBusinessDays = true;
    //最大年份
    private static final int MAX_YEAR = 2500;
    //最小年份
    private static final int MIN_YEAR = 1970;
    
    /** 用于通过Intent传递任务ID */
    public static final String INTENT_TASK_ID = "intent.crontask.id";
    
    public String seconds     = "0";
    public String minutes     = "0";
    public String hours       = "8";
    public String months      = "*";
    public String daysOfWeek  = "*";
    public String daysOfMonth = "*";
    public String years       = "*";
    public String startDate   = ""; //暂时没用
    public String endDate     = "";   //暂时没用 
    public int    byDow       = 1;  //默认按周
    public JSONObject extra;
    
    public boolean[] bHours;
    public boolean[] bSeconds;
    public boolean[] bMinutes;
    public boolean[] bMonths;
    public boolean[] bDaysOfWeek;
    public boolean[] bDaysOfMonth;
    public boolean[] bYears;
    
    private long time = -1;
     

	public CronTask() {
		super(); 
		id = -1;
		enabled = DISABLED;
		extra = new JSONObject();
	}
    
    public CronTask(Cursor c) {
    	super();
    	id = c.getInt(c.getColumnIndex(Columns.ID));
		title = c.getString(c.getColumnIndex(Columns.TITLE));
		description = c.getString(c.getColumnIndex(Columns.DESCRIPTION));
		enabled = c.getInt(c.getColumnIndex(Columns.ENABLED));
		byDow = c.getInt(c.getColumnIndex(Columns.BY_DOW));
		extra = getJsonObject(c.getString(c.getColumnIndex(Columns.EXTRA)));
		parse(c.getString(c.getColumnIndex(Columns.CRON_PATTEN)));
    }
    
    /**
     * 解析类似cronTab格式的字符串(形如"* * * * * *")，并用解析结果设置CronTask的各属性
     * 
     * @param cron cronTab格式的字符串
     */
    public void parse(String cron) throws TaskException {
    	
		boolean[] bHours = new boolean[24];
        boolean[] bMinutes = new boolean[60];
        boolean[] bMonths = new boolean[12];
        boolean[] bDaysOfWeek = new boolean[8]; //周中的第1天在Calendar中的值是1
        boolean[] bDaysOfMonth = new boolean[32]; //月份中的第1天在Calendar中的值是1
        boolean[] bYears = new boolean[MAX_YEAR];
        
        StringTokenizer tokenizer = new StringTokenizer(cron);
        Log.d(Consts.TAG,":CronStr:"+cron);
        int numTokens = tokenizer.countTokens();
        if(numTokens!=6) {
        	Log.d(Consts.TAG,cron+":Crontab string format invalid,6 token at least");
            throw new TaskException(cron+":Crontab string format invalid,6 token at least");
        }
        
        for(int i = 0; tokenizer.hasMoreElements(); i++) {
            String token = tokenizer.nextToken();
            switch(i) {
                case 0:     // 分钟
                    parseToken(token,bMinutes,false);
					setbMinutes(bMinutes);
					setMinutes(token);
                    break;
                case 1:     // 小时
                    parseToken(token,bHours,false);
					setbHours(bHours);
					setHours(token);
                    break;
                case 2:     // 日期
                    parseToken(token,bDaysOfMonth,true);
					setbDaysOfMonth(bDaysOfMonth);
					setDaysOfMonth(token);
                    break;
                case 3:     // 月份
                    parseToken(token,bMonths,true);
					setbMonths(bMonths);
					setMonths(token);
                    break;
                case 4:     // 星期几
                    parseToken(token,bDaysOfWeek,false);
					setbDaysOfWeek(bDaysOfWeek);
					setDaysOfWeek(token);
                    break;
                case 5:     // 年份
                    parseToken(token,bYears,false);
					setbYears(bYears);
					setYears(token);
                    break;
                default:
                    break;
            }
        }
    }
    
    public String unparse() {
    	
    	String space = " ";
    	
    	return new StringBuffer()
    			.append(minutes).append(space)
    			.append(hours).append(space)
    			.append(daysOfMonth).append(space)
    			.append(months).append(space)
    			.append(daysOfWeek).append(space)
    			.append(years).toString() ;
    }
    
	public void parseToken(String token, boolean[] arrayBool,
			boolean bBeginInOne) throws TaskException {

		int i;
		int index;
		int each = 1;
		try {

			// Look for step first
			index = token.indexOf("/");
			if (index > 0) {
				each = Integer.parseInt(token.substring(index + 1));
				if (each == 0) {
					throw new TaskException("字符串格式错误,类似 */0 的格式是错误的.");
				}
				token = token.substring(0, index);
			}

			if (token.equals("*")) {
				for (i = 0; i < arrayBool.length; i += each) {
					arrayBool[i] = true;
				}
				return;
			}

			index = token.indexOf(",");
			if (index > 0) {
				StringTokenizer tokenizer = new StringTokenizer(token, ",");
				while (tokenizer.hasMoreElements()) {
					parseToken(tokenizer.nextToken(), arrayBool, bBeginInOne);
				}
				return;
			}

			index = token.indexOf("-");
			if (index > 0) {
				int start = Integer.parseInt(token.substring(0, index));
				int end = Integer.parseInt(token.substring(index + 1));

				if (bBeginInOne) {
					start--;
					end--;
				}
				for (int j = start; j <= end; j += each)
					arrayBool[j] = true;
				return;
			}

			int iValue = Integer.parseInt(token);
			if (bBeginInOne) {
				iValue--;
			}
			arrayBool[iValue] = true;
			return;
		} catch (Exception e) {
			Log.d(Consts.TAG, "Token:"+token);
			throw new TaskException("CronTab parse " + token);
		}
	}
	
	/**
	 * 计算下次执行的时间(unix时间戳) 暂时无用
	 * @return
	 */
	private Date getNextTime(Date afterTime) {
		
		//初始化日历
		Calendar cal = new java.util.GregorianCalendar();
		cal.setTime(afterTime);
		cal.set(Calendar.MILLISECOND, 0);
		
		/**
		 * 
		 */
		if (cal.get(Calendar.YEAR)>MAX_YEAR) {
			//年份超出范围
			return null;
		}
		
		//get minute
		int min  = cal.get(Calendar.MINUTE);
		int hour = cal.get(Calendar.HOUR_OF_DAY);
		int nextMin = getNextTrue(bMinutes, min);
		if (min>nextMin) { 
			cal.set(Calendar.HOUR_OF_DAY, ++hour);
		}
		cal.set(Calendar.MINUTE, nextMin);
		
		//get hour
		hour = cal.get(Calendar.HOUR_OF_DAY);
		int nextHour = getNextTrue(bHours, hour);		
		if(hour>nextHour) {
			cal.set(Calendar.MINUTE, getFirstTrue(bMinutes));
			cal.add(Calendar.DAY_OF_MONTH, 1);
		}
		cal.set(Calendar.HOUR_OF_DAY, hour);
		
		//dom,dow & month
		int dom   = cal.get(Calendar.DAY_OF_MONTH);
		int dow   = cal.get(Calendar.DAY_OF_WEEK);
		
		int nextDom = getNextTrue(bDaysOfMonth, dom);
		int nextDow = getNextTrue(bDaysOfWeek,  dow); 
		
		
		int month = cal.get(Calendar.MONTH); 
		int nextMonth = getNextTrue(bMonths, month);
		int year = cal.get(Calendar.YEAR);
		int nextYear  = getNextTrue(bYears, year);
		
		
		return null;
	}
	
	private static int getNextTrue(boolean[] v, int current) {
		
		int size = v.length;
		for (int i=current; i<size; i++) {
			if (v[i]) return i;
		}
		
		return  getFirstTrue(v);
	}
	
	private static int getFirstTrue(boolean[] v) {
		
		int size = v.length;
		//如果是年份,则从MIN_YEAR(如2010)开始计算,当前年份之前的匹配基本是无意义的
		for (int i=(size==MAX_YEAR)?MIN_YEAR:0; i<size; i++) {
			if (v[i]) return i;
		}
		
		return 0;
	}

    private static boolean isLeapYear(int year) {
        return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
    }
	
	private static int getLastDayOfMonth(int monthNum, int year) {

        switch (monthNum) {
            case 0:
                return 31;
            case 1:
                return (isLeapYear(year)) ? 29 : 28;
            case 2:
                return 31;
            case 3:
                return 30;
            case 4:
                return 31;
            case 5:
                return 30;
            case 6:
                return 31;
            case 7:
                return 31;
            case 8:
                return 30;
            case 9:
                return 31;
            case 10:
                return 30;
            case 11:
                return 31;
            default:
                throw new IllegalArgumentException("Illegal month number: "
                        + monthNum);
        }
    }
	
	/**
	 * 插入新任务
	 * @param task
	 * @return
	 */
	public static CronTask insert(Context context, CronTask task) {
		ContentValues values = buildValues(task);
		Uri uri = context.getContentResolver().insert(CronTask.CONTENT_URI, values);
		task.id = (int) ContentUris.parseId(uri);
		return task;
	}
	/**
	 * 更新任务
	 * @param task
	 * @return
	 */
	public static int update(Context context, CronTask task) {
		ContentValues values = buildValues(task);
		int ret = context.getContentResolver().update(
				ContentUris.withAppendedId(CronTask.CONTENT_URI, task.id), values, null, null);
		return ret;
	}
	/**
	 * 删除任务
	 * @param task_id
	 * @return
	 */
	public static int delete(Context context, int task_id) {
		int ret = context.getContentResolver().delete(
				ContentUris.withAppendedId(CronTask.CONTENT_URI, task_id), null, null);
		return ret;
	}
	
	private static ContentValues buildValues(CronTask task) {
		
		ContentValues values = new ContentValues(10);

		values.put(Columns.TASK_TYPE, task.type);
		values.put(Columns.TITLE, task.title);
		values.put(Columns.DESCRIPTION, task.description);
		values.put(Columns.CRON_PATTEN, task.unparse());
		values.put(Columns.STARTDATE, task.startDate);
		values.put(Columns.ENDDATE, task.endDate);
		values.put(Columns.ENABLED, task.enabled);
		values.put(Columns.BY_DOW, task.byDow);
		values.put(Columns.EXTRA,task.extra.toString());
		
		return values;
	}
	
	public static class Columns implements BaseColumns {
		
		public static final String ID = "_ID"; 
		/** 任务类型  */
		public static final String TASK_TYPE = "TASK_TYPE"; 
		/** 标题 */
		public static final String TITLE = "TITLE";
		/** 备注 */
		public static final String DESCRIPTION = "DESCRIPTION";
		/** 执行计划 */
		public static final String CRON_PATTEN = "CRON_PATTEN";
		/** 是否按周 */
		public static final String BY_DOW = "BY_DOW";
		/** 开始时间 */
		public static final String STARTDATE = "STARTDATE";
		/** 结束时间 */
		public static final String ENDDATE = "ENDDATE";
		/** 有效/禁用 */
		public static final String ENABLED = "ENABLED";
		/** 附加信息 */
		public static final String EXTRA = "EXTRA";
		
		public static final String[] QUERY_COLUMNS = {
			ID, TASK_TYPE, TITLE, DESCRIPTION, CRON_PATTEN, BY_DOW, STARTDATE, ENDDATE, ENABLED, EXTRA };

	}
	
	private JSONObject getJsonObject(String jsonStr) {
		JSONObject ret = null;
		try {
			ret = new JSONObject(jsonStr);
		} catch(JSONException e) {
			Log.e(Consts.TAG, "parse json string error, jsonstr: "+ jsonStr);
			throw new IllegalArgumentException(e);
		}
		
		return ret;
	}
	
    public long getTime() {
		return time;
	}
	public void setTime(long time) {
		this.time = time;
	}

	public boolean isRunInBusinessDays() {
		return runInBusinessDays;
	}
	public void setRunInBusinessDays(boolean runInBusinessDays) {
		this.runInBusinessDays = runInBusinessDays;
	}
	public String getSeconds() {
		return seconds;
	}
	public void setSeconds(String seconds) {
		this.seconds = seconds;
	}
	public String getHours() {
		return hours;
	}
	public void setHours(String hours) {
		this.hours = hours;
	}
	public String getMinutes() {
		return minutes;
	}
	public void setMinutes(String minutes) {
		this.minutes = minutes;
	}
	public String getMonths() {
		return months;
	}
	public void setMonths(String months) {
		this.months = months;
	}
	public String getDaysOfWeek() {
		return daysOfWeek;
	}
	public void setDaysOfWeek(String daysOfWeek) {
		this.daysOfWeek = daysOfWeek;
	}
	public String getDaysOfMonth() {
		return daysOfMonth;
	}
	public void setDaysOfMonth(String daysOfMonth) {
		this.daysOfMonth = daysOfMonth;
	}
	public String getYears() {
		return years;
	}
	public void setYears(String years) {
		this.years = years;
	}
	public String getStartDate() {
		return startDate;
	}
	public void setStartDate(String startDate) {
		this.startDate = startDate;
	}
	public String getEndDate() {
		return endDate;
	}
	public void setEndDate(String endDate) {
		this.endDate = endDate;
	}
	public boolean[] getbHours() {
		return bHours;
	}
	public void setbHours(boolean[] bHours) {
		this.bHours = bHours;
	}
	public boolean[] getbSeconds() {
		return bSeconds;
	}
	public void setbSeconds(boolean[] bSeconds) {
		this.bSeconds = bSeconds;
	}
	public boolean[] getbMinutes() {
		return bMinutes;
	}
	public void setbMinutes(boolean[] bMinutes) {
		this.bMinutes = bMinutes;
	}
	public boolean[] getbMonths() {
		return bMonths;
	}
	public void setbMonths(boolean[] bMonths) {
		this.bMonths = bMonths;
	}
	public boolean[] getbDaysOfWeek() {
		return bDaysOfWeek;
	}
	public void setbDaysOfWeek(boolean[] bDaysOfWeek) {
		this.bDaysOfWeek = bDaysOfWeek;
	}
	public boolean[] getbDaysOfMonth() {
		return bDaysOfMonth;
	}
	public void setbDaysOfMonth(boolean[] bDaysOfMonth) {
		this.bDaysOfMonth = bDaysOfMonth;
	}
	public boolean[] getbYears() {
		return bYears;
	}
	public void setbYears(boolean[] bYears) {
		this.bYears = bYears;
	}
	
	public boolean isByDow() {
		return byDow==0?false:true;
	}
	
	public void setByDow(int bydow) {
		byDow = bydow;
	}
	
	public void setByDow(boolean bydow) {
		byDow = bydow?1:0;
	}
 
}
